package com.gitee.dufafei.plugin.vfs;

import com.gitee.dufafei.plugin.consts.Const;
import com.gitee.dufafei.plugin.core.PluginException;
import com.gitee.dufafei.plugin.variable.Variable;
import com.gitee.dufafei.plugin.variable.VariableSpace;
import com.gitee.dufafei.plugin.vfs.config.FileSystemConfigFactory;
import org.apache.commons.vfs2.*;
import org.apache.commons.vfs2.cache.WeakRefFilesCache;
import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;

import java.io.File;
import java.io.IOException;
import java.util.Comparator;

public class FileVFS {

    private static final FileVFS FILE_VFS = new FileVFS();
    private static VariableSpace defaultVariableSpace;
    private static DefaultFileSystemManager fsm;

    private FileVFS() {
        defaultVariableSpace = new Variable();
        fsm = new StandardFileSystemManager();
        try {
            fsm.setFilesCache(new WeakRefFilesCache());
            fsm.init();
        } catch (FileSystemException e) {
            e.printStackTrace();
        }
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                fsm.close();
            } catch (Exception ignored) {
                // Exceptions can be thrown due to a closed classloader
            }
        }));
    }

    public static FileVFS getInstance() { return FILE_VFS; }

    public static FileSystemManager getFileSystemManager() { return fsm; }

    public static FileObject getFileObject(String vfsFilename) throws PluginException { return getFileObject(vfsFilename, defaultVariableSpace); }

    public static FileObject getFileObject(String vfsFilename, VariableSpace space) throws PluginException { return getFileObject(vfsFilename, null, space); }

    public static FileObject getFileObject(String vfsFilename,
                                           FileSystemOptions fsOptions,
                                           VariableSpace space) throws PluginException {
        try {
            //vfsFilename = URLDecoder.decode(vfsFilename, "UTF-8");
            FileSystemManager fsManager = getFileSystemManager();
            // We have one problem with VFS: if the file is in a subdirectory of the current one: some_dir/some_file
            // In that case, VFS doesn't parse the file correctly.
            // We need to put file: in front of it to make it work.
            // However, how are we going to verify this?
            //
            // We are going to see if the filename starts with one of the known protocols like file: zip: ram: smb: jar: etc.
            // If not, we are going to assume it's a file.
            boolean relativeFilename = true;
            String[] schemes = fsManager.getSchemes();
            for (int i = 0; i < schemes.length && relativeFilename; i++) {
                if (vfsFilename.startsWith(schemes[i] + ":")) {
                    // We have a VFS URL, load any options for the file system driver
                    fsOptions = FileSystemConfigFactory.buildFsOptions(vfsFilename, schemes[i], fsOptions, space);
                    relativeFilename = false;
                }
            }
            String filename;
            if (vfsFilename.startsWith("\\\\")) {
                File file = new File(vfsFilename);
                filename = file.toURI().toString();
            } else {
                if (relativeFilename) {
                    File file = new File(vfsFilename);
                    filename = file.getAbsolutePath();
                } else {
                    filename = vfsFilename;
                }
            }
            FileObject fileObject;
            if (fsOptions != null) {
                fileObject = fsManager.resolveFile(filename, fsOptions);
            } else {
                fileObject = fsManager.resolveFile(filename);
            }
            return fileObject;
        } catch (IOException e) {
            throw new PluginException("Unable to get VFS File object for filename '" + cleanseFilename(vfsFilename) + "' : " + e.getMessage());
        }
    }

    /**
     * Private method for stripping password from filename when a FileObject
     * can not be obtained.
     * getFriendlyURI(FileObject) or getFriendlyURI(String) are the public
     * methods.
     */
    private static String cleanseFilename(String vfsFilename) {
        return vfsFilename.replaceAll(":[^:@/]+@", ":<password>@");
    }

    public static String getFileName(FileObject fileObject) {
        FileName fileName = fileObject.getName();
        String root = fileName.getRootURI();
        // nothing we can do about non-normal files.
        if (!root.startsWith("file:")) {
            return fileName.getURI();
        }
        // we'll see 4 forward slashes for a windows/smb network share
        if (root.startsWith("file:////")) {
            return fileName.getURI();
        }
        if (root.endsWith(":/")) {  // Windows
            root = root.substring(8, 10);
        } else { // *nix & OSX
            root = "";
        }
        String fileString = root + fileName.getPath();
        if (!"/".equals(Const.FILE_SEPARATOR)) {
            fileString = Const.replace(fileString, "/", Const.FILE_SEPARATOR);
        }
        return fileString;
    }

    public static Comparator<FileObject> getComparator() {
        return (o1, o2) -> {
            String filename1 = getFileName(o1);
            String filename2 = getFileName(o2);
            return filename1.compareTo(filename2);
        };
    }
}
